#ifndef __DYNAMIC_STRING_H__
#define __DYNAMIC_STRING_H__
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "dynamic_array.h"

// It is an equivalent of shift command from bash. It basically pops a command line
// argument from the beginning.
char *dyn_str_shift_args(int *argc, char ***argv);

// --- Example generic implementation
// typedef struct dyn_array {
//     void* items;
//     u64 length;
//     u64 capacity;
// } dyn_array_t;
//

typedef struct String_Builder {
    char *items;
    u64 length;
    u64 capacity;
} String_Builder;

// Long form append to a SB
#define sb_append(sb, buf, size) dyn_array_append_many(sb, buf, size)

// Append a null terminated C "string" on a String_Builder
#define sb_append_cstr(sb, cstr)  \
    do {                              \
        const char *s = (cstr);       \
        size_t n = strlen(s);         \
        dyn_array_append_many(sb, s, n); \
    } while (0)

// Add a null terminator to a SB; makes SB.items a "C String"
#define sb_append_null(sb) dyn_array_append_many(sb, '\0', 1)

#define sb_free(sb) free((sb).items); (sb).length=0; (sb).capacity=0;
bool sb_read_entire_file(const char *path, String_Builder *sb);

typedef struct String_View {
    u64 length;
    const char *data;
} String_View;

const char *temp_sv_to_cstr(String_View sv);

String_View sv_chop_by_delim(String_View *sv, char delim);
String_View sv_trim(String_View sv);
bool sv_eq(String_View a, String_View b);
String_View sv_from_cstr(const char *cstr);
String_View sv_from_parts(const char *data, size_t count);

// printf macros for String_View
// USAGE:
//   String_View name = ...;
//   printf("Name: "SV_Fmt"\n", SV_Arg(name));
#ifndef SV_Fmt
#define SV_Fmt "%.*s"
#endif // SV_Fmt
#ifndef SV_Arg
#define SV_Arg(sv) (int) (sv).length, (sv).data
#endif // SV_Arg


#ifdef DYN_STR_IMPLEMENTATION 
#define return_defer(value) do { result = (value); goto defer; } while(0)

bool sb_read_entire_file(const char *path, String_Builder *sb) {
    bool result = true;

    FILE *f = fopen(path, "rb");
    if (f == NULL)                 return_defer(false);
    if (fseek(f, 0, SEEK_END) < 0) return_defer(false);
    long m = ftell(f);
    if (m < 0)                     return_defer(false);
    if (fseek(f, 0, SEEK_SET) < 0) return_defer(false);

    size_t new_count = sb->length + m;
    if (new_count > sb->capacity) {
        sb->items = realloc(sb->items, new_count);
        assert(sb->items != NULL && "Buy more RAM lool!!");
        sb->capacity = new_count;
    }

    fread(sb->items + sb->length, m, 1, f);
    if (ferror(f)) {
        // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case.
        return_defer(false);
    }
    sb->length = new_count;

defer:
    if (!result) fprintf(stderr, "Could not read file %s: %s", path, strerror(errno));
    if (f) fclose(f);
    return result;
}

String_View sv_chop_by_delim(String_View *sv, char delim) {
    size_t i = 0;
    while (i < sv->length && sv->data[i] != delim) {
        i += 1;
    }

    String_View result = sv_from_parts(sv->data, i);

    if (i < sv->length) {
        sv->length -= i + 1;
        sv->data  += i + 1;
    } else {
        sv->length -= i;
        sv->data  += i;
    }

    return result;
}

String_View sv_from_parts(const char *data, size_t length) {
    String_View sv;
    sv.length = length;
    sv.data = data;
    return sv;
}

String_View sv_trim_left(String_View sv)
{
    size_t i = 0;
    while (i < sv.length && isspace(sv.data[i])) {
        i += 1;
    }

    return sv_from_parts(sv.data + i, sv.length - i);
}

String_View sv_trim_right(String_View sv) {
    size_t i = 0;
    while (i < sv.length && isspace(sv.data[sv.length - 1 - i])) {
        i += 1;
    }

    return sv_from_parts(sv.data, sv.length - i);
}

String_View sv_trim(String_View sv) {
    return sv_trim_right(sv_trim_left(sv));
}

String_View sv_from_cstr(const char *cstr) {
    return sv_from_parts(cstr, strlen(cstr));
}

bool sv_eq(String_View a, String_View b) {
    if (a.length != b.length) {
        return false;
    } else {
        return memcmp(a.data, b.data, a.length) == 0;
    }
}

char *dyn_str_shift_args(int *argc, char ***argv) {
    assert(*argc > 0);
    char *result = **argv;
    (*argv) += 1;
    (*argc) -= 1;
    return result;
}
#endif // DYN_STR_IMPLEMENTATION

#endif // __DYNAMIC_STRING_H__